#region Using directives

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;
using System.Text;

#endregion

namespace Ndo
{
    internal class SynchronizeFactory<T> : BoxFactory<T>
    {
        internal FieldBuilder rfb;
        internal const string SyncRootFieldName = "__syncRoot";
        internal static bool isAvoid(MemberInfo mi)
        {
            return mi.GetCustomAttributes(typeof(AvoidSynchronizeAttribute), false).Length > 0;
        }
        internal SynchronizeFactory(string typename)
        {
            Type[] ts = new Type[] { typeof(T), typeof(object) };
            //TypeBuild
            tb = BoxHelper.BoxModuleBuilder.DefineType(
                typename,
                TypeAttributes.NotPublic);

            //FieldBuilder
            fb = tb.DefineField(TargetFieldName, typeof(T), BoxHelper.PrivateFieldAttribute);
            rfb = tb.DefineField(SyncRootFieldName, typeof(object), BoxHelper.PrivateFieldAttribute);

            //ConstructorBuild
            ConstructorBuilder cb = tb.DefineConstructor(
                MethodAttributes.Public,
                CallingConventions.Standard,
                ts);
            ILGenerator g = cb.GetILGenerator();
            g.Emit(OpCodes.Ldarg_0);
            g.Emit(OpCodes.Call, BaseConstructor);
            g.Emit(OpCodes.Ldarg_0);
            g.Emit(OpCodes.Ldarg_1);
            g.Emit(OpCodes.Stfld, fb);
            g.Emit(OpCodes.Ldarg_0);
            g.Emit(OpCodes.Ldarg_2);
            g.Emit(OpCodes.Stfld, rfb);
            g.Emit(OpCodes.Ret);

            if (typeof(T).IsInterface)
            {
                tb.AddInterfaceImplementation(typeof(T));
                ImplementMembers(typeof(T), true, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            }
            else
            {
                tb.SetParent(typeof(T));
                ImplementMembers(typeof(T), false, BindingFlags.Public | BindingFlags.Instance);
            }
            foreach(Type t in typeof(T).GetInterfaces())
            {
                tb.AddInterfaceImplementation(t);
                ImplementMembers(t, true, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            }
 

            //Implement of ISynchronized<T>
            tb.AddInterfaceImplementation(typeof(IBoxed<T>));
            tb.AddInterfaceImplementation(typeof(ISynchronized<T>));
            MethodInfo Getter;
            MethodBuilder mb;
            //Implement of TargetGetter
            Getter = typeof(IBoxed<T>).GetProperty("Target").GetGetMethod(false);
            mb = tb.DefineMethod(Getter.Name, BoxHelper.InterfaceMethodAttribute, typeof(T), BoxHelper.EmptyTypes);
            g = mb.GetILGenerator();
            g.Emit(OpCodes.Ldarg_0);
            g.Emit(OpCodes.Ldfld, fb);
            g.Emit(OpCodes.Ret);
            tb.DefineMethodOverride(mb, Getter);
            //Implement of SyncRootGetter
            Getter = typeof(ISynchronized<T>).GetProperty("SyncRoot").GetGetMethod(false);
            mb = tb.DefineMethod(Getter.Name, BoxHelper.InterfaceMethodAttribute, typeof(object), BoxHelper.EmptyTypes);
            g = mb.GetILGenerator();
            g.Emit(OpCodes.Ldarg_0);
            g.Emit(OpCodes.Ldfld, rfb);
            g.Emit(OpCodes.Ret);
            tb.DefineMethodOverride(mb, Getter);

            //Finished
            CI = tb.CreateType().GetConstructor(ts);
        }
        private void ImplementMembers(Type type, bool isInterface, BindingFlags bindingFlags)
        {
            bool avoid;
            //MethodBuild
            foreach (MethodInfo mi in typeof(T).GetMethods(BoxHelper.AllMemberBindingFlags))
                if (!mi.IsSpecialName && mi.IsVirtual)
                {
                    ImplementMethod(mi, isInterface, isAvoid(mi));
                }

            //PropertyBuild
            foreach (PropertyInfo pi in typeof(T).GetProperties(BoxHelper.AllMemberBindingFlags))
            {
                avoid = isAvoid(pi);
                MethodInfo mi = pi.GetGetMethod(false);
                if (mi != null)
                {
                    ImplementMethod(mi, isInterface, avoid | isAvoid(mi));
                }
                mi = pi.GetSetMethod(false);
                if (mi != null)
                {
                    ImplementMethod(mi, isInterface, avoid | isAvoid(mi));
                }
            }

            //EventBuild
            foreach (EventInfo ei in typeof(T).GetEvents(BoxHelper.AllMemberBindingFlags))
            {
                avoid = isAvoid(ei);
                MethodInfo mi = ei.GetAddMethod(false);
                if (mi != null)
                {
                    ImplementMethod(mi, isInterface, avoid | isAvoid(mi));
                }
                mi = ei.GetRemoveMethod(false);
                if (mi != null)
                {
                    ImplementMethod(mi, isInterface, avoid | isAvoid(mi));
                }
            }
        }
        private void ImplementMethod(MethodInfo mi, bool isInterface, bool avoid)
        {
            MethodAttributes ma;
            if (isInterface)
                ma = BoxHelper.InterfaceMethodAttribute;
            else
                ma = mi.Attributes;
            if (avoid)
            {
                ImplementMethodInvoke(ma, mi);
            }
            else
            {
                ImplementMethodSync(ma, mi);
            }
        }
        private void ImplementMethodSync(MethodAttributes ma, MethodInfo mi)
        {
            Type[] ts = BoxHelper.GetParamsTypes(mi.GetParameters());
            MethodBuilder mb = tb.DefineMethod(mi.Name, ma, mi.ReturnType, ts);
            ILGenerator g = mb.GetILGenerator();
            //local for syncroot
            g.DeclareLocal(typeof(object));

            if (mi.ReturnType != typeof(void))
                g.DeclareLocal(mi.ReturnType);

            g.Emit(OpCodes.Ldarg_0);
            g.Emit(OpCodes.Ldfld, rfb);
            g.Emit(OpCodes.Dup);
            g.Emit(OpCodes.Stloc_0);
            g.Emit(OpCodes.Call, BoxHelper.ThreadEnterMethod);

            g.BeginExceptionBlock();

            g.Emit(OpCodes.Ldarg_0);
            g.Emit(OpCodes.Ldfld, fb);
            switch (ts.Length)
            {
                case 0:
                    break;
                case 1:
                    g.Emit(OpCodes.Ldarg_1);
                    break;
                case 2:
                    g.Emit(OpCodes.Ldarg_1);
                    g.Emit(OpCodes.Ldarg_2);
                    break;
                default:
                    g.Emit(OpCodes.Ldarg_1);
                    g.Emit(OpCodes.Ldarg_2);
                    g.Emit(OpCodes.Ldarg_3);
                    for (byte i = 4; i <= ts.Length; i++)
                        g.Emit(OpCodes.Ldarg_S, i);
                    break;
            }
            g.Emit(OpCodes.Callvirt, mi);
            if (mi.ReturnType != typeof(void))
                g.Emit(OpCodes.Stloc_1);

            g.BeginFinallyBlock();

            g.Emit(OpCodes.Ldloc_0);
            g.Emit(OpCodes.Call, BoxHelper.ThreadExitMethod);

            g.EndExceptionBlock();

            if (mi.ReturnType != typeof(void))
                g.Emit(OpCodes.Ldloc_1);
            g.Emit(OpCodes.Ret);

            tb.DefineMethodOverride(mb, mi);
        }
    }
}
